/*
 *  WilBase64XferBytes.cpp
 *  Wilson
 *
 *  Created by Sebastian Orfino on 8/23/11.
 *  Copyright 2011 __MyCompanyName__. All rights reserved.
 *
 */

// Interface includes:
// none. 

// Project includes:
#include "WilBase64XferBytes.h"
#include <algorithm>
#include "base64.h"
#include <boost/shared_array.hpp>

using namespace std;

WilBase64XferBytes::WilBase64XferBytes() : buffer_pos_(0)
{
}

WilBase64XferBytes::~WilBase64XferBytes()
{
}

uint32 WilBase64XferBytes::Write(void* buffer, uint32 num)
{
	ASSERT(buffer!=nil);

	if (!buffer_stream_) return 0;
	if (!num) return 0;

	//pongo al stream en el lugar donde estaba la ultima vez que se leyo o escribio.
	buffer_stream_.seekp(0,ios::seekdir(buffer_pos_));
	
	//std::vector<uchar> v(num);
	
	//std::copy((uchar*)buffer,((uchar*)buffer)+num,v.begin());
	
	std::string str64(base64_encode((uchar*)buffer,num));

	buffer_stream_ << str64;
	//std::copy(str64.begin(),str64.end(),buffer_.begin()+buffer_pos_);
	
//	buffer_ += Encode(v);
	
	buffer_pos_ += buffer_stream_.tellp();//+= str64.length();
	
	return str64.length();
	
}

uint32 WilBase64XferBytes::Read(void* buffer, uint32 num)
{
	//chequeo si lo que me piden es mas de lo que hay,
	using namespace std;
	size_t largo = largo_buffer();
	
	if (num+buffer_pos_ > largo)//buffer_.length())
		num = largo - buffer_pos_;
	
	if (num<1) return 0;
	
	//pongo al stream en el lugar donde estaba la ultima vez que se leyo o escribio.
	buffer_stream_.seekg(0,ios::seekdir(buffer_pos_));
	
	//boost::buffer 
	boost::shared_array<char> buffer_local(new char[num]);
	
	buffer_stream_.read(buffer_local.get(), num);
	//std::vector<uchar> v = Decode(buffer_local.get());

	std::string v = base64_decode(buffer_local.get());
	
	std::copy(v.begin(),v.end(),(uchar*)buffer);
	
	buffer_pos_ -= buffer_stream_.tellg();//+= num;
	
	return v.size();
}

size_t WilBase64XferBytes::largo_buffer() 
{
	size_t temp_pos = buffer_pos_;
	buffer_stream_.seekg(0,ios::end);
	size_t largo = buffer_stream_.tellg();
	buffer_stream_.seekg(temp_pos,ios::beg);
	
	return largo;
}

uint32 WilBase64XferBytes::Seek(int32 numberOfBytes, SeekFromWhere fromHere)
{
	// como para nosotros el pubtero de lectura y el de escritura son el mismo, prefiero mantener
	//una variable independiente (buffer_pos_) y luego posicionar el stream en el Read o en el Write a buffer_pos_.
	switch (fromHere) {
		case kSeekFromStart:
			buffer_pos_ = numberOfBytes;
			break;
		case kSeekFromEnd:
		{
			
			buffer_pos_ = largo_buffer() - numberOfBytes;
			break;
		}
		case kSeekFromCurrent:
			buffer_pos_ += numberOfBytes;
			break;
	}
	
	return numberOfBytes;
}

void WilBase64XferBytes::Flush()
{
	if (buffer_stream_)
		buffer_stream_.flush();
}

StreamState WilBase64XferBytes::GetStreamState(void)
{
	return buffer_pos_ > largo_buffer() ? kStreamStateEOF : kStreamStateGood;
}

void WilBase64XferBytes::SetEndOfStream()
{
	//size_t largo = buffer_stream_.str().length();
	//buffer_stream_.seekp(0,ios::end); es al pedo porque siempre me manejo con buffer_pos_
	buffer_pos_ = largo_buffer()+1;//buffer_.size();
}



//la siguiente implementacion de base64 la saque de http://www.kbcafe.com/articles/HowTo.Base64.pdf

char WilBase64XferBytes::Encode(uchar uc)
{

	if (uc < 26) 
		return 'A'+uc;

	if (uc < 52) 
		return 'a'+(uc-26);

	if (uc < 62) 
		return '0'+(uc-52);

	if (uc == 62) 
		return '+'; 

	return '/';
}


uchar WilBase64XferBytes::Decode(char c)
{
	if (c>= 'A' && c<='Z')
		return c - 'A';
	if (c>= 'a' && c<='z')
		return c - 'a' + 26;
	
	if (c>='0' && c<='9')
		return c - '0' + 52;
	if (c == '+')
		return 62;
	
	return 63;
}


bool WilBase64XferBytes::IsBase64(char c)
{

	if (c >= 'A' && c <= 'Z') {
		return true;
	}
	if (c >= 'a' && c <= 'z') {
		return true;
	}
	if (c >= '0' && c <= '9') {
		return true;
	}
	if (c == '+') {
		return true;
	};
	if (c == '/') {
		return true;
	};
	if (c == '=') 
		return true; 

	return false;
}


std::string WilBase64XferBytes::Encode(const std::vector<uchar>& vby)
{
	std::string retval;
	if (vby.size() == 0) 
		return retval;
	
	
	for (int i=0;i<vby.size();i+=3) {
		uchar by1=0,by2=0,by3=0; 
		by1 = vby[i]; 
		
		if (i+1<vby.size()) 
			by2 = vby[i+1];
		
		if (i+2<vby.size()) 
			by3 = vby[i+2];

		uchar by4=0,by5=0,by6=0,by7=0; 
		by4 = by1>>2; 
		by5 = ((by1&0x3)<<4)|(by2>>4); 
		by6 = ((by2&0xf)<<2)|(by3>>6);
		by7 = by3 & 0x3f;
		
		retval += Encode(by4); 
		retval += Encode(by5);
		
		if (i+1<vby.size()) 
			retval += Encode(by6);
		else {
			retval += "=";
		}
		
		if (i+2<vby.size()) {
			retval += Encode(by7);
		} 
		else {
			retval += "=";
		};
	
		if (i % (76/4*3) == 0) {
			retval += "\r\n";
		}
	}
	
	return retval;
}

std::vector<uchar> WilBase64XferBytes::Decode(const std::string& _str)
{
	std::string str; 
	for (int j=0;j<_str.length();j++) {
		if (IsBase64(_str[j])) {
			str += _str[j];
		}
	} 
	
	std::vector<uchar> retval;
	if (str.length() == 0) {
		return retval;
	}

	for (int i=0;i<str.length();i+=4) {
		char c1='A',c2='A',c3='A',c4='A'; 
		c1 = str[i]; 
		if (i+1<str.length()) {
			c2 = str[i+1];
		};
		if (i+2<str.length()) {
		c3 = str[i+2];
		}; 
		if (i+3<str.length()) {
			c4 = str[i+3];
		};
		uchar by1=0,by2=0,by3=0,by4=0;
		by1 = Decode(c1); 
		by2 = Decode(c2); 
		by3 = Decode(c3);
		by4 = Decode(c4); 
		
		retval.push_back( (by1<<2) | (by2>>4) );
		if (c3 != '=') {
			retval.push_back( ((by2&0xf)<<4)|(by3>>2) );
		}
		if (c4 != '=') {
			retval.push_back( ((by3&0x3)<<6)|by4 );
		}
	}
	
	return retval;
}



